package com.mathdroid.model.stats;

import java.util.LinkedList;

import com.mathdroid.model.Model;
import com.mathdroid.model.Stats;
import com.mathdroid.model.enums.EItemStat;

//TODO offensive "defense chance reduction" through accuracy

/**
 * This calculates "bonus damages", percentages, total stats, etc.
 * Each simulation has to use its own CharacterStats object.
 */
public abstract class ACharacterStats {
	
	/**
	 * The level of the character.
	 */
	private static final double LEVEL = 50d;

	/**
	 * Temporary stat adjustments like buffs ands procs. Those can be negative,
	 * too.
	 */
	private Stats dynamicAdjustments = new Stats();
	
	/**
	 * Those are the accumulated stats from all items and modules plus base 
	 * stats. Those stats do not change for the entire simulation. Every stat
	 * is mapped, even it is "0".
	 */
	private Stats staticStats = new Stats();
	
	/**
	 * Create a new CharacterStats object.
	 */
	public ACharacterStats() {
		Stats itemStats = Model.getProfile().getEquipedItems().getTotalStats();
		Stats baseStats = Model.getProfile().getBaseStats();
		
		/* Map all stats and combine base stats with item stats. */
		for (EItemStat s : EItemStat.getAllStats()) {
			if (!itemStats.containsKey(s)) {
				itemStats.put(s, 0);
			}
			if (!baseStats.containsKey(s)) {
				baseStats.put(s, 0);
			}
			if (!this.dynamicAdjustments.containsKey(s)) {
				this.dynamicAdjustments.put(s, 0);
			}
			
			this.staticStats.put(s, itemStats.get(s) + baseStats.get(s));
		}
	}
	
	/**
	 * Get the current total stats of one type.
	 * @param stat Stat type.
	 * @return Current value.
	 */
	public final int getTotalStats(final EItemStat stat) {		
		return this.dynamicAdjustments.get(stat) + this.staticStats.get(stat);
	}
	
	/* ************************** STRING DATA LISTS ************************* */
	
	/**
	 * Get all of the String data lists. <b>The first element of the lists
	 * should be the title of the content.</b>
	 * @return All String data lists.
	 */
	public abstract LinkedList<LinkedList<String>> getAllDataLists();
	
	/**
	 * Get the bonus damage and healing values.
	 * Get a list of Strings for the JTree output on the character tab.
	 * This is specialization specific data.
	 * @return List of node-text.
	 */
	protected abstract LinkedList<String> getDataListBonusDamageHealing();
	
	/**
	 * Get casting speed related values.
	 * Get a list of Strings for the JTree output on the character tab.
	 * This is specialization specific data.
	 * @return List of node-text.
	 */
	protected abstract LinkedList<String> getDataListCastingSpeed();
	
	/**
	 * Get defensive values.
	 * Get a list of Strings for the JTree output on the character tab.
	 * This is specialization specific data.
	 * @return List of node-text.
	 */
	protected abstract LinkedList<String> getDataListDefensive();
	
	/**
	 * Get companion related values.
	 * Get a list of Strings for the JTree output on the character tab.
	 * This is specialization specific data.
	 * @return List of node-text.
	 */
	protected abstract LinkedList<String> getDataListCompanion();
	
	/**
	 * Get PvP related values.
	 * Get a list of Strings for the JTree output on the character tab.
	 * This is specialization specific data.
	 * @return List of node-text.
	 */
	protected abstract LinkedList<String> getDataListPvP();
	
	/**
	 * Get accuracy related values.
	 * Get a list of Strings for the JTree output on the character tab.
	 * This is specialization specific data.
	 * @return List of node-text.
	 */
	protected abstract LinkedList<String> getDataListHit();
	
	/**
	 * Get critical strike related values.
	 * Get a list of Strings for the JTree output on the character tab.
	 * This is specialization specific data.
	 * @return List of node-text.
	 */
	protected abstract LinkedList<String> getDataListCriticalStrike();
	
	/* **************************** CALCULATIONS **************************** */
	
	/**
	 * Get the bonus damage from the primary stat 
	 * (Strength/Willpower/Aim/Cunning).
	 * @return Damage bonus.
	 */
	public abstract double getDamageBonusFromPrimaryStat();
	
	/**
	 * Get the bonus damage from the secondary stat. Some specializations
	 * get bonus damage from another stat.
	 * @return Damage bonus.
	 */
	public abstract double getDamageBonusFromSecondaryStat();
	
	/**
	 * Get the damage bonus from the Force- or Techpower stat.
	 * @return Damage bonus.
	 */
	public abstract double getDamageBonusFromForceOrTechPower();
	
	/**
	 * Get the bonus damage from the Power stat.
	 * @return Bonus damage.
	 */
	public abstract double getDamageBonusFromPower();
	
	/**
	 * Get the bonus healing from the primary stat 
	 * (Strength/Willpower/Aim/Cunning).
	 * @return Healing bonus.
	 */
	public abstract double getHealingBonusFromPrimaryStat();
	
	/**
	 * Get the healing bonus from the Force- or Techpower stat.
	 * @return Healing bonus.
	 */
	public abstract double getHealingBonusFromForceOrTechPower();
	
	/**
	 * Get the healing bonus from the Power stat.
	 * @return Bonus healing.
	 */
	public abstract double getHealingBonusFromPower();
	
	/**
	 * Get the general critical strike chance in percent. This is applied to 
	 * Force-/Tech-Attacks.
	 * SCREENSHOT: stats-methods/getCritChancePercent.jpg
	 * @return General critical strike chance.
	 */
	public abstract double getCritChancePercent();
	
	/**
	 * Get the ranged critical strike chance in percent.
	 * SCREENSHOT: stats-methods/getRangedCritChancePercent.jpg
	 * @return Ranged critical strike chance.
	 */
	public abstract double getRangedCritChancePercent();
	
	/**
	 * Get the melee critical strike chance in percent.
	 * @return Melee critical strike chance.
	 */
	public abstract double getMeleeCritChancePercent();
	
	/**
	 * Get the force critical strike chance in percent.
	 * SCREENSHOT: stats-methods/getForceCritChancePercent.jpg
	 * @return Force critical strike chance.
	 */
	public abstract double getForceCritChancePercent();
	
	/**
	 * Get the ranged minimum damage of the primary weapon.
	 * SCREENSHOT: stats-methods/getRangedDamagePrimaryMin.jpg
	 * @return Minimum damage.
	 */
	public abstract double getRangedDamagePrimaryMin();
	
	/**
	 * Get the ranged maximum damage of the primary weapon.
	 * SCREENSHOT: stats-methods/getRangedDamagePrimaryMin.jpg ("Min", too).
	 * @return Maximum damage.
	 */
	public abstract double getRangedDamagePrimaryMax();
	
	/**
	 * Get the total ranged bonus damage.
	 * SCREENSHOT: stats-methods/getRangedBonusDamage.jpg
	 * @return Total ranged bonus damage.
	 */
	public abstract double getRangedBonusDamage();
	
	/**
	 * Get the total bonus damage for tech or force damage.
	 * SCREENSHOT: stats-methods/getTotalBonusDamageTechForce.jpg
	 * @return Total tech/force bonus damage.
	 */
	public abstract double getTotalBonusDamageTechForce();
	
	/**
	 * Get the total bonus healing for tech or force damage.
	 * SCREENSHOT: stats-methods/getTotalBonusHealingTechForce.jpg
	 * @return Total tech/force bonus healing.
	 */
	public abstract double getTotalBonusHealingTechForce();
	
	/**
	 * Get the Shield/Glance chance in percent.
	 * @return Shield/Glance chance.
	 */
	public abstract double getShieldGlanceChancePercent();
	
	/**
	 * Get the Shield/Glance absorbtion in percent.
	 * @return Shield/Glance absorbtion.
	 */
	public abstract double getShieldGlanceAbsorbtionPercent();
	
	/* *********** THE FOLLOWING STATS DO NOT DEPEND ON THE CLASS *********** */
	
	/**
	 * Get the damage bonus to critical strikes in percent.
	 * SCREENSHOT: 
	 * - stats-methods/getCritDamageBonusPercent.jpg
	 * - stats-methods/getCritDamageBonusPercent_2.jpg
	 * @return Critical damage bonus.
	 */
	public final double getCritDamageBonusPercent() {
		return 50d + 50d * (1d - Math.pow(1d - (0.01 / 0.5),
			((this.getTotalStats(EItemStat.SURGE_RATING) / LEVEL) / 0.1)));
	}
	
	/**
	 * Get the basic accuracy in percent.
	 * SCREENSHOT: stats-methods/getBasicAccuracyPercent.jpg
	 * @return Basic accuracy.
	 */
	public final double getBasicAccuracyPercent() {
		return 90d + 30d * (1d - Math.pow(1d - (0.01 / 0.3),
			((this.getTotalStats(EItemStat.ACCURACY_RATING) / LEVEL) / 0.55)));
	}
	
	/**
	 * Get the basic Off-Hand accuracy in percent.
	 * @return Basic Off-Hand accuracy.
	 */
	public final double getBasicOffHandAccuracyPercent() {
		return 57d + 30d * (1d - Math.pow(1d - (0.01 / 0.3),
			((this.getTotalStats(EItemStat.ACCURACY_RATING) / LEVEL) / 0.55)));
	}
	
	/**
	 * Get the special accuracy in percent.
	 * SCREENSHOT: stats-methods/getSpecialAccuracyPercent.jpg
	 * @return Special accuracy.
	 */
	public final double getSpecialAccuracyPercent() {
		return 100d + 30d * (1d - Math.pow(1d - (0.01 / 0.3),
			((this.getTotalStats(EItemStat.ACCURACY_RATING) / LEVEL) / 0.55)));
	}
	
	/**
	 * Get the special Off-Hand accuracy in percent.
	 * @return Special Off-Hand accuracy.
	 */
	public final double getSpecialOffHandAccuracyPercent() {
		return 67d + 30d * (1d - Math.pow(1d - (0.01 / 0.3),
			((this.getTotalStats(EItemStat.ACCURACY_RATING) / LEVEL) / 0.55)));
	}
	
	/**
	 * Get the activation speed divisor (value is >= 1.0).
	 * SCREENSHOT: stats-methods/getActivationSpeedDivisor.jpg
	 * @return Activation speed divisor.
	 */
	public final double getActivationSpeedDivisor() {
		return 1d + 0.3d * (1d - Math.pow(1d - (0.01 / 0.3),
			((this.getTotalStats(EItemStat.ALACRITY_RATING) / LEVEL) / 0.55)));
	}
	
	/**
	 * Get the armor damage reduction in percent. <b>The cap is 75%.</b>
	 * SCREENSHOT: stats-methods/getArmorDamageReductionInPercent.jpg
	 * @return Armor damage reduction.
	 */
	public final double getArmorDamageReductionInPercent() {
		final double armor = this.getTotalStats(EItemStat.ARMOR);
		double reduction = armor / (armor + 200d * LEVEL + 800d) * 100d;
		
		if (reduction > 75d) {
			reduction = 75d;
		}
		
		return reduction;
	}
	
	/**
	 * Get the defense chance in percent.
	 * SCREENSHOT: stats-methods/getDefenseChancePercent.jpg
	 * @return Defense chance.
	 */
	public final double getDefenseChancePercent() {
		return 5d + 30d * (1d - Math.pow(1d - (0.01 / 0.3),
			((this.getTotalStats(EItemStat.DEFENSE_RATING) / LEVEL) / 0.55)));
	}
	
	/**
	 * Get the PvP bonus damage in percent.
	 * SCREENSHOT: stats-methods/getPvPBonusDamagePercent.jpg
	 * @return PvP bonus damage.
	 */
	public final double getPvPBonusDamagePercent() {
		return 9d * (1d - Math.pow(1d - (0.01 / 0.09),
			((this.getTotalStats(EItemStat.EXPERTISE_RATING) / LEVEL) / 0.5)));
	}
	
	/**
	 * Get the PvP damage reduction in percent.
	 * SCREENSHOT: stats-methods/getPvPDamageReductionPercent.jpg
	 * @return PvP damage reduction.
	 */
	public final double getPvPDamageReductionPercent() {
		return 9d * (1d - Math.pow(1d - (0.01 / 0.09),
			((this.getTotalStats(EItemStat.EXPERTISE_RATING) / LEVEL) / 0.5)));
	}
	
	/**
	 * Get the PvP bonus healing in percent.
	 * SCREENSHOT: stats-methods/getPvPBonusHealingPercent.jpg
	 * @return PvP bonus healing.
	 */
	public final double getPvPBonusHealingPercent() {
		//TODO is this up to date?
		return 9d * (1d - Math.pow(1d - (0.01 / 0.09),
			((this.getTotalStats(EItemStat.EXPERTISE_RATING) / LEVEL) / 0.5)));
	}
	
	/**
	 * Get the maximum health.
	 * SCREENSHOT: stats-methods/getMaxHealth.jpg
	 * @return Maximum health.
	 */
	public final double getMaxHealth() {
		return 2500d + this.getTotalStats(EItemStat.ENDURANCE) * 10d;
	}
	
	/**
	 * Get the health regen.
	 * @return health regen.
	 */
	public final double getHealthRegen() {
		return /* TODO BaseHealthRegen [+] */ 
		this.getTotalStats(EItemStat.ENDURANCE) * 0.03;
	}
	
	/**
	 * Get the companion's bonus health.
	 * @return Companion's bonus health.
	 */
	public final double getBonusCompanionHealth() {
		return this.getTotalStats(EItemStat.PRESENCE) * 5d;
	}
	
	/**
	 * Get the companion's bonus damage.
	 * @return Companion's bonus damage.
	 */
	public final double getBonusCompanionDamage() {
		return this.getTotalStats(EItemStat.PRESENCE) * 0.2;
	}
	
	/**
	 * Get the companion's bonus healing.
	 * @return Companion's bonus healing.
	 */
	public final double getBonusCompanionHealing() {
		return this.getTotalStats(EItemStat.PRESENCE) * 0.14;
	}
}
